package com.ls.lsojcodesandbox;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.ls.lsojcodesandbox.model.ExecuteMessage;
import com.ls.lsojcodesandbox.utils.JavaCodeSandboxTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

//@Component
public class JavaDockerSandboxBackup extends JavaCodeSandboxTemplate {
    private static final String imageName = "openjdk:8-alpine";
    private static final long TIME_OUT = 5000L;

    @Override
    public List<ExecuteMessage> runFile(File userCodeFile, List<String> inputList, String lastFileName) {
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
        //1.创建客户端
        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
        //2.拉取镜像(如果需要)并创建容器
        List<Image> hasImages = dockerClient.listImagesCmd().exec();
        boolean hasImage = false;
        for(Image image : hasImages) {
            if(image.getRepoTags()[0].equals(imageName)) {
                hasImage = true;
                break;
            }
        }
        PullImageResultCallback pullImageResultCallback = new PullImageResultCallback(){
            @Override
            public void onNext(PullResponseItem item) {
                System.out.println("拉取镜像成功");
                super.onNext(item);
            }
        };
        // 如果没有镜像就需要拉取镜像
        if(!hasImage) {
            try {
                dockerClient.pullImageCmd(imageName).exec(pullImageResultCallback).awaitCompletion();
            } catch (InterruptedException e) {
                System.out.println("拉取镜像异常");
                throw new RuntimeException(e);
            }
            System.out.println("拉取镜像成功");
        } else {
            System.out.println("已有镜像，不需要拉取");
        }
        //创建容器
        CreateContainerCmd createContainerCmd = dockerClient.createContainerCmd(imageName);
        HostConfig hostConfig = new HostConfig();
        hostConfig.withCpuCount(1L).withMemory(100 * 1000L * 1000L);
        hostConfig.setBinds(new Bind(userCodeParentPath, new Volume("/app")));
        CreateContainerResponse creatContainerResponse = createContainerCmd
                .withHostConfig(hostConfig)
                .withAttachStdout(true)
                .withAttachStderr(true)
                .withTty(true)
                .exec();
        System.out.println("创建容器成功");
        //启动容器
        String containerId = creatContainerResponse.getId();
        dockerClient.startContainerCmd(containerId).exec();

        //3.在容器中执行Java命令,并获取内存占用情况和程序运行时间
        List<ExecuteMessage> executeMessages = new ArrayList<>();
        for(String input : inputList) {
            ExecuteMessage executeMessage = new ExecuteMessage();
            //创建输入文件
            File file = new File( userCodeParentPath + "/input.txt");
            try {
                FileWriter fileWriter = new FileWriter(file);
                fileWriter.write(input);
                fileWriter.flush();
            } catch (IOException e) {
                throw new RuntimeException("输入新建文件夹失败" + e);
            }

            String[] cmdArray = {"sh", "-c", "java -cp /app Main < /app/input.txt"};
            //创建在容器内执行的命令并exec
            //除此之外还需要获取运行时的内存和程序执行的时间
            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd(cmdArray)
                    .withAttachStderr(true)
                    .withAttachStdout(true)
                    .exec();
            //统计最大内存信息
            final long[] memory = {0L};
            //创建内存监视器
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            ResultCallback<Statistics> memoryStatistics = statsCmd.exec(new ResultCallback<Statistics>() {
                @Override
                public void onStart(Closeable closeable) {

                }

                @Override
                public void onNext(Statistics object) {
                    System.out.println("内存占用:" + object.getMemoryStats().getUsage());
                    memory[0] = Math.max(memory[0], object.getMemoryStats().getUsage());
                }

                @Override
                public void onError(Throwable throwable) {

                }

                @Override
                public void onComplete() {

                }

                @Override
                public void close() throws IOException {

                }
            });

            //超时控制和程序输出信息获取
            //获取程序输出的信息，包括正常输出和错误输出
            final boolean[] timeout = {false};
            final String[] errorMessage = {""};
            final String[] message = {""};
            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
                @Override
                public void onComplete() {
                    timeout[0] = true;
                    super.onComplete();
                }

                @Override
                public void onNext(Frame frame) {
                    StreamType streamType = frame.getStreamType();
                    //输出的是错误信息
                    if(streamType.equals(StreamType.STDERR)) {
                        errorMessage[0] = new String(frame.getPayload());
                    } else{
                        message[0] = new String(frame.getPayload());
                    }
                    super.onNext(frame);
                }
            };

            //正式在容器中执行命令并统计程序执行过程中的内存信息
            try {
                //获取程序执行时间
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                dockerClient.execStartCmd(execCreateCmdResponse.getId())
                        .exec(execStartResultCallback)
                        .awaitCompletion(TIME_OUT, TimeUnit.MILLISECONDS);
                stopWatch.stop();
                executeMessage.setTime(stopWatch.getLastTaskTimeMillis());
                statsCmd.close();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //删除文件
            if(!file.delete()) {
                System.out.println("删除文件失败");
            }
            executeMessage.setMemory(memory[0]);
            executeMessage.setMessage(message[0]);
            executeMessage.setErrorMessage(errorMessage[0]);

            executeMessages.add(executeMessage);
        }

        //4.删除容器并返回结果
        try {
            dockerClient.stopContainerCmd(containerId).exec();
            dockerClient.removeContainerCmd(containerId).exec();
        } catch (Exception e) {
            System.out.println("停止容器或者删除容器失败");
        }
        return executeMessages;
    }
}
